package org.grayrabbit.cms.util;

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.BitSet;

import org.grayrabbit.cms.ComponentManager;
import org.grayrabbit.cms.properties.ApplicationProperties;


public class URLCodec {
	protected static byte ESCAPE_CHAR = 37;

	protected static final BitSet WWW_FORM_URL = new BitSet(256);

	public static byte[] encodeUrl(BitSet urlsafe, byte[] bytes) {
		if (bytes == null) {
			return null;
		}
		if (urlsafe == null) {
			urlsafe = WWW_FORM_URL;
		}

		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		for (int i = 0; i < bytes.length; i++) {
			int b = bytes[i];
			if (b < 0) {
				b = 256 + b;
			}
			if (urlsafe.get(b)) {
				if (b == 32) {
					b = 43;
				}
				buffer.write(b);
			} else {
				buffer.write(ESCAPE_CHAR);
				char hex1 = Character.toUpperCase(Character.forDigit(
						b >> 4 & 0xF, 16));
				char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF,
						16));
				buffer.write(hex1);
				buffer.write(hex2);
			}
		}
		return buffer.toByteArray();
	}

	public static byte[] decodeUrl(byte[] bytes)
			throws UnsupportedEncodingException {
		if (bytes == null) {
			return null;
		}
		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		for (int i = 0; i < bytes.length; i++) {
			int b = bytes[i];
			if (b == 43) {
				buffer.write(32);
			} else if (b == ESCAPE_CHAR) {
				try {
					i++;
					int u = Character.digit((char) bytes[i], 16);
					i++;
					int l = Character.digit((char) bytes[i], 16);
					if ((u == -1) || (l == -1)) {
						throw new UnsupportedEncodingException(
								"Invalid URL encoding");
					}
					buffer.write((char) ((u << 4) + l));
				} catch (ArrayIndexOutOfBoundsException e) {
					throw new UnsupportedEncodingException(
							"Invalid URL encoding");
				}
			} else {
				buffer.write(b);
			}
		}
		return buffer.toByteArray();
	}

	public static byte[] encode(byte[] bytes) {
		return encodeUrl(WWW_FORM_URL, bytes);
	}

	public static byte[] decode(byte[] bytes)
			throws UnsupportedEncodingException {
		return decodeUrl(bytes);
	}

	public static String encode(String pString, String charset)
			throws UnsupportedEncodingException {
		if (pString == null) {
			return null;
		}
		return new String(encode(pString.getBytes(charset)), "US-ASCII");
	}

	public static String encode(String pString)
			throws UnsupportedEncodingException {
		if (pString == null) {
			return null;
		}

		return encode(pString, getDefaultCharset());
	}

	public static String decode(String pString, String charset)
			throws UnsupportedEncodingException {
		if (pString == null) {
			return null;
		}
		return new String(decode(pString.getBytes("US-ASCII")), charset);
	}

	public static String decode(String pString)
			throws UnsupportedEncodingException {
		if (pString == null) {
			return null;
		}
		return decode(pString, getDefaultCharset());
	}

	public static Object encode(Object pObject)
			throws UnsupportedEncodingException {
		if (pObject == null) {
			return null;
		}
		if ((pObject instanceof byte[])) {
			return encode((byte[]) (byte[]) pObject);
		}
		if ((pObject instanceof String)) {
			return encode((String) pObject);
		}

		throw new UnsupportedEncodingException("Objects of type "
				+ pObject.getClass().getName() + " cannot be URL encoded");
	}

	public static Object decode(Object pObject)
			throws UnsupportedEncodingException {
		if (pObject == null) {
			return null;
		}
		if ((pObject instanceof byte[])) {
			return decode((byte[]) (byte[]) pObject);
		}
		if ((pObject instanceof String)) {
			return decode((String) pObject);
		}

		throw new UnsupportedEncodingException("Objects of type "
				+ pObject.getClass().getName() + " cannot be URL decoded");
	}

	private static String getDefaultCharset() {
		ApplicationProperties appProps = (ApplicationProperties) ComponentManager.getInstance().getApplicationProperties();
		return appProps.getEncoding();
	}

	static {
		for (int i = 97; i <= 122; i++) {
			WWW_FORM_URL.set(i);
		}
		for (int i = 65; i <= 90; i++) {
			WWW_FORM_URL.set(i);
		}

		for (int i = 48; i <= 57; i++) {
			WWW_FORM_URL.set(i);
		}

		WWW_FORM_URL.set(45);
		WWW_FORM_URL.set(95);
		WWW_FORM_URL.set(46);
		WWW_FORM_URL.set(42);

		WWW_FORM_URL.set(32);
	}
}
